﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using System.Windows.Media.Animation;
using System.Diagnostics;
using Project3D.my3D;
using Project3D.my3D.Structure;
using Project3D.my3D.Base;
using Project3D.my3D.Geometric;



namespace Project3D.UI
{
    /// <summary>
    /// Logique d'interaction pour MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        private GeometryModel3D mGeometry;
        private Model3DGroup group = new Model3DGroup();
        private ModelVisual3D model = new ModelVisual3D();
        private Viewport3D viewport = new Viewport3D();
        private bool inSilhouette = false;

        private PerspectiveCamera myCamera = new PerspectiveCamera();

        private bool mDown;
        private Point mLastPos;

        MeshGeometry3D meshGUI;
        myMesh mesh = new myMesh();

        public MainWindow()
        {
            InitializeComponent();

            //Adding event-handlers for mouse events.
            Mouse.AddMouseMoveHandler(this, myMouseMove);
            Mouse.AddMouseDownHandler(this, myMouseDown);
            Mouse.AddMouseUpHandler(this, myMouseUp);
            Mouse.AddMouseWheelHandler(this, myMouseWheel);
            Keyboard.AddKeyDownHandler(this, myDownKeyboard);
            Keyboard.AddKeyUpHandler(this, myUpKeyboard);

            //Setting up the Camera.
            myCamera.Position = new Point3D(0, 0, 2);
            myCamera.LookDirection = new Vector3D(0, 0, -1);
            myCamera.FieldOfView = 60;
            viewport.Camera = myCamera;

            //Setting up the Lighting.
            DirectionalLight myDirLight = new DirectionalLight(Colors.White, new Vector3D(-3, -2, -1));
            group.Children.Add(myDirLight);
            DirectionalLight myDirLight2 = new DirectionalLight(Colors.White, new Vector3D(3, 2, 1));
            group.Children.Add(myDirLight2);

            //Setting up WPF GUI classes.
            group.Transform = new Transform3DGroup();
            model.Content = group;
            viewport.Children.Add(model);
            this.Content = viewport;

            //Now whatever is in "group" will be drawn on-screen. Read and draw the mesh now.
            //setGUIMesh();
        }

        private void myMouseMove(object sender, MouseEventArgs e)
        {
            Point pos = Mouse.GetPosition(viewport);
            Point actualPos = actualPos = new Point(pos.X - viewport.ActualWidth / 2, viewport.ActualHeight / 2 - pos.Y);
            double dx = actualPos.X - mLastPos.X, dy = actualPos.Y - mLastPos.Y;

            if (!Keyboard.IsKeyDown(Key.LeftCtrl) && mDown)
            {
                double mouseAngle = 0;
                if (dx != 0 && dy != 0)
                {
                    mouseAngle = Math.Asin(Math.Abs(dy) / Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2)));
                    if (dx < 0 && dy > 0) mouseAngle += Math.PI / 2;
                    else if (dx < 0 && dy < 0) mouseAngle += Math.PI;
                    else if (dx > 0 && dy < 0) mouseAngle += Math.PI * 1.5;
                }
                else if (dx == 0 && dy != 0) mouseAngle = Math.Sign(dy) > 0 ? Math.PI / 2 : Math.PI * 1.5;
                else if (dx != 0 && dy == 0) mouseAngle = Math.Sign(dx) > 0 ? 0 : Math.PI;

                double axisAngle = mouseAngle + Math.PI / 2;

                Vector3D axis = new Vector3D(Math.Cos(axisAngle) * 4, Math.Sin(axisAngle) * 4, 0);

                double rotation = 0.01 * Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));

                Transform3DGroup xgroup = group.Transform as Transform3DGroup;
                QuaternionRotation3D r = new QuaternionRotation3D(new Quaternion(axis, rotation * 180 / Math.PI));

                xgroup.Children.Add(new RotateTransform3D(r));
                mLastPos = actualPos;
            }
            if (Keyboard.IsKeyDown(Key.LeftCtrl) && mDown)
            {
                myCamera.Position = new Point3D(myCamera.Position.X - dx / 100D, myCamera.Position.Y - dy / 100D, myCamera.Position.Z);
                viewport.Camera = myCamera;
                mLastPos = actualPos;
            }

            if (inSilhouette)
                this.setGUISilhouette();
        }

        private void myMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.LeftButton != MouseButtonState.Pressed) return;
            mDown = true;
            Point pos = Mouse.GetPosition(viewport);
            mLastPos = new Point(pos.X - viewport.ActualWidth / 2, viewport.ActualHeight / 2 - pos.Y);
        }

        private void myMouseUp(object sender, MouseButtonEventArgs e)
        {
            mDown = false;

            //get the point entered by the user by clicking with LeftCtrl down.
            if (Keyboard.IsKeyDown(Key.LeftCtrl))
            {
                Point mousePos = e.GetPosition(viewport);
                HitTestResult result = VisualTreeHelper.HitTest(viewport, mousePos);
                RayMeshGeometry3DHitTestResult rayMeshResult = result as RayMeshGeometry3DHitTestResult;
                if (rayMeshResult != null)
                {
                    Transform3D transformOnModelVisual3D = group.Transform;
                    GeneralTransform3D t2 = (transformOnModelVisual3D as GeneralTransform3D).Inverse;
                    Point3D pointhit = t2.Transform(rayMeshResult.PointHit);
                    //pointhit now contains the coordinates of the point lying on the mesh that the user entered by mouse-click
                    //you can use this point to do vertex-deletions or edge contracts etc. 
                    Console.WriteLine("coordinates of the hit point: " + pointhit.X + ", " + pointhit.Y + ", " + pointhit.Z);
                }
            }
        }

        private void myMouseWheel(object sender, MouseWheelEventArgs e)
        {
            myCamera.Position = new Point3D(myCamera.Position.X, myCamera.Position.Y, myCamera.Position.Z - e.Delta / 250D);
            viewport.Camera = myCamera;
        }

        private void myDownKeyboard(object sender, KeyEventArgs e)
        {
            Console.WriteLine(e.Key.ToString());
            if (e.Key == Key.Escape)
                Close();
            else if (e.Key == Key.R)
            {
                myCamera.Position = new Point3D(0, 0, 2);
                myCamera.LookDirection = new Vector3D(0, 0, -1);
                myCamera.FieldOfView = 60;
                viewport.Camera = myCamera;
            }
            else if (e.Key == Key.O)
            {
                Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
                dlg.DefaultExt = ".obj";
                dlg.Filter = "Obj documents (.obj)|*.obj";

                Nullable<bool> result = dlg.ShowDialog();

                if (result == true)
                {
                    mesh.read(dlg.FileName);
                    setGUIMesh();
                    group.Transform = new Transform3DGroup();
                }
            }
            else if (e.Key == Key.S)
            {
                Microsoft.Win32.SaveFileDialog dialog = new Microsoft.Win32.SaveFileDialog();
                dialog.DefaultExt = ".obj";
                dialog.Filter = "Obj documents (.obj)|*.obj";

                Nullable<bool> result = dialog.ShowDialog();

                if (result == true)
                {
                    this.mesh.write(dialog.FileName);
                    //this.mesh.write_triangularmesh(dialog.FileName + ".triangular.obj");
                }
            }
            else if (e.Key == Key.T)
            {
                //mesh.check();
                //mesh.edgeSplit(mesh.halfedges[0]);
                //mesh.check();

                //mesh.check();
                //mesh.faceSplit(mesh.faces[0]);
                //mesh.check();
                //setGUIWireframe();

                //mesh.check();
                //mesh.triangulate();
                //mesh.check();
                //setGUIWireframe();

                //mesh.check();
                mesh.subdivisionCatmullClark();
                //mesh.check();
                setGUIMesh();
                //setGUIWireframe();

                //myLine3D line = new myLine3D();
                //line.p = new myPoint3D(1, 1, 1);
                //line.v = new myVector3D(1, 1, 1);
                //foreach (myVertex v in mesh.vertices)
                //    myUtil.computeRotation(v.point, line, (System.Math.PI * 30) / 180, v.point);
                //this.setGUIMesh();


                //mesh.check();
                //mesh.edgeContract(mesh.halfedges[0]);
                //mesh.check();
                //setGUIMesh();
                //setGUIWireframe();
                /*
                BoxQuadric box = new BoxQuadric(mesh);
                box.ShowDialog();
                mesh.computeSimplification(mesh.vertices.Count - box.GetResultEdge());
                mesh.check();
                setGUIMesh();*/

                //setGUIWireframe();

                inSilhouette = false;
            }
            else if (e.Key == Key.C)
                mesh.check();
            else if (e.Key == Key.W)
            {
                setGUIWireframe();
                inSilhouette = false;
            }
            else if (e.Key == Key.N)
            {
                setGUIMesh();
                inSilhouette = false;
            }
            else if (e.Key == Key.H)
            {
                setGUISilhouette();
                inSilhouette = true;
            }
            else if (e.Key == Key.M)
            {
                setGUINormal();
                inSilhouette = false;
            }
        }

        private void myUpKeyboard(object sender, KeyEventArgs e)
        {
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {


        }

        private void setGUIMesh()
        {
            int i;
            group.Children.Clear();

            for (i = 0; i < mesh.vertices.Count; i++)
                mesh.vertices[i].v_id = i;

            meshGUI = new MeshGeometry3D();

            int position = 0;
            //mesh.computeVertexNormals();
            for (i = 0; i < mesh.faces.Count; i++)
            {
                myHalfedge e = mesh.faces[i].adjacent_halfedge;
                myHalfedge e2 = e.next.next;
                while (e2 != e)
                {
                    Point3D p0 = new Point3D(mesh.vertices[e.next.source.v_id].point.X, mesh.vertices[e.next.source.v_id].point.Y, mesh.vertices[e.next.source.v_id].point.Z);
                    Point3D p1 = new Point3D(mesh.vertices[e2.source.v_id].point.X, mesh.vertices[e2.source.v_id].point.Y, mesh.vertices[e2.source.v_id].point.Z);
                    Point3D p2 = new Point3D(mesh.vertices[e2.next.source.v_id].point.X, mesh.vertices[e2.next.source.v_id].point.Y, mesh.vertices[e2.next.source.v_id].point.Z);

                    meshGUI.Positions.Add(p0); position++;
                    meshGUI.Positions.Add(p1); position++;
                    meshGUI.Positions.Add(p2); position++;

                    meshGUI.TriangleIndices.Add(position - 3);
                    meshGUI.TriangleIndices.Add(position - 2);
                    meshGUI.TriangleIndices.Add(position - 1);


                    //meshGUI.Normals.Add(new Vector3D(mesh.vertices[e.target.v_id].dX, mesh.vertices[e.target.v_id].dY, mesh.vertices[e.target.v_id].dZ));
                    //meshGUI.Normals.Add(new Vector3D(mesh.vertices[e2.source.v_id].dX, mesh.vertices[e2.source.v_id].dY, mesh.vertices[e2.source.v_id].dZ));
                    //meshGUI.Normals.Add(new Vector3D(mesh.vertices[e2.target.v_id].dX, mesh.vertices[e2.target.v_id].dY, mesh.vertices[e2.target.v_id].dZ));

                    //myVector3D tmpvec = new myVector3D();
                    //mesh.computeNormal(mesh.vertices[e.target.v_id], mesh.vertices[e2.source.v_id], mesh.vertices[e2.target.v_id], tmpvec);
                    //Vector3D normal = new Vector3D(tmpvec.dX, tmpvec.dY, tmpvec.dZ);

                    //meshGUI.Normals.Add(normal);
                    //meshGUI.Normals.Add(normal);
                    //meshGUI.Normals.Add(normal);

                    e2 = e2.next;
                }
            }
            Material material = new DiffuseMaterial(new SolidColorBrush(Colors.DarkKhaki));
            mGeometry = new GeometryModel3D(meshGUI, material);
            group.Children.Add(mGeometry);

            group.Children.Add(new DirectionalLight(Colors.White, new Vector3D(-3, -2, -1)));
            group.Children.Add(new DirectionalLight(Colors.White, new Vector3D(3, 2, 1)));
        }

        private void setGUIWireframe()
        {
            group.Children.Clear();
            MeshGeometry3D wireframeMesh = new MeshGeometry3D();

            for (int i = 0; i < mesh.halfedges.Count; i++)
            {
                myPoint3D p1 = mesh.halfedges[i].source.point;
                myPoint3D p2 = mesh.halfedges[i].target.point;
                setLinesegment(wireframeMesh, p1, p2);
            }
            GeometryModel3D wireframeGeometry = new GeometryModel3D(wireframeMesh, new DiffuseMaterial(new SolidColorBrush(Colors.Blue)));
            group.Children.Add(wireframeGeometry);
        }

        private void setLinesegment(MeshGeometry3D mym, myPoint3D p1, myPoint3D p2, double t = 0.005)
        {
            int position = mym.Positions.Count;

            mym.Positions.Add(new Point3D(p1.X - t, p1.Y, p1.Z)); position++;
            mym.Positions.Add(new Point3D(p1.X + t, p1.Y, p1.Z)); position++;
            mym.Positions.Add(new Point3D(p2.X + t, p2.Y, p2.Z)); position++;
            mym.Positions.Add(new Point3D(p2.X - t, p2.Y, p2.Z)); position++;

            mym.TriangleIndices.Add(position - 4);
            mym.TriangleIndices.Add(position - 3);
            mym.TriangleIndices.Add(position - 2);
            mym.TriangleIndices.Add(position - 2);
            mym.TriangleIndices.Add(position - 3);
            mym.TriangleIndices.Add(position - 4);

            mym.TriangleIndices.Add(position - 2);
            mym.TriangleIndices.Add(position - 1);
            mym.TriangleIndices.Add(position - 4);
            mym.TriangleIndices.Add(position - 4);
            mym.TriangleIndices.Add(position - 1);
            mym.TriangleIndices.Add(position - 2);


            mym.Positions.Add(new Point3D(p1.X, p1.Y - t, p1.Z)); position++;
            mym.Positions.Add(new Point3D(p1.X, p1.Y + t, p1.Z)); position++;
            mym.Positions.Add(new Point3D(p2.X, p2.Y + t, p2.Z)); position++;
            mym.Positions.Add(new Point3D(p2.X, p2.Y - t, p2.Z)); position++;

            mym.TriangleIndices.Add(position - 4);
            mym.TriangleIndices.Add(position - 3);
            mym.TriangleIndices.Add(position - 2);
            mym.TriangleIndices.Add(position - 2);
            mym.TriangleIndices.Add(position - 3);
            mym.TriangleIndices.Add(position - 4);

            mym.TriangleIndices.Add(position - 4);
            mym.TriangleIndices.Add(position - 1);
            mym.TriangleIndices.Add(position - 2);

            mym.Positions.Add(new Point3D(p1.X, p1.Y, p1.Z - t)); position++;
            mym.Positions.Add(new Point3D(p1.X, p1.Y, p1.Z + t)); position++;
            mym.Positions.Add(new Point3D(p2.X, p2.Y, p2.Z + t)); position++;
            mym.Positions.Add(new Point3D(p2.X, p2.Y, p2.Z - t)); position++;

            mym.TriangleIndices.Add(position - 4);
            mym.TriangleIndices.Add(position - 3);
            mym.TriangleIndices.Add(position - 2);
            mym.TriangleIndices.Add(position - 2);
            mym.TriangleIndices.Add(position - 3);
            mym.TriangleIndices.Add(position - 4);


            mym.TriangleIndices.Add(position - 2);
            mym.TriangleIndices.Add(position - 1);
            mym.TriangleIndices.Add(position - 4);
            mym.TriangleIndices.Add(position - 4);
            mym.TriangleIndices.Add(position - 1);
            mym.TriangleIndices.Add(position - 2);
        }

        private void setGUISilhouette()
        {
            int i;
            group.Children.Clear();
            group.Children.Add(new DirectionalLight(Colors.White, new Vector3D(-3, -2, -1)));
            group.Children.Add(new DirectionalLight(Colors.White, new Vector3D(3, 2, 1)));

            for (i = 0; i < this.mesh.vertices.Count; i++)
                this.mesh.vertices[i].v_id = i;

            MeshGeometry3D facesMesh = new MeshGeometry3D();

            int position = 0;
            for (i = 0; i < this.mesh.faces.Count; i++)
            {
                myHalfedge e = this.mesh.faces[i].adjacent_halfedge;
                myHalfedge e2 = e.next.next;
                while (e2 != e)
                {
                    Point3D p0 = new Point3D(this.mesh.vertices[e.target.v_id].point.X, this.mesh.vertices[e.target.v_id].point.Y, this.mesh.vertices[e.target.v_id].point.Z);
                    Point3D p1 = new Point3D(this.mesh.vertices[e2.source.v_id].point.X, this.mesh.vertices[e2.source.v_id].point.Y, this.mesh.vertices[e2.source.v_id].point.Z);
                    Point3D p2 = new Point3D(this.mesh.vertices[e2.target.v_id].point.X, this.mesh.vertices[e2.target.v_id].point.Y, this.mesh.vertices[e2.target.v_id].point.Z);

                    facesMesh.Positions.Add(p0); position++;
                    facesMesh.Positions.Add(p1); position++;
                    facesMesh.Positions.Add(p2); position++;

                    facesMesh.TriangleIndices.Add(position - 3);
                    facesMesh.TriangleIndices.Add(position - 2);
                    facesMesh.TriangleIndices.Add(position - 1);

                    myVector3D tmpvec = new myVector3D();
                    myUtil.computeNormal(this.mesh.vertices[e.target.v_id], this.mesh.vertices[e2.source.v_id], this.mesh.vertices[e2.target.v_id], tmpvec);
                    Vector3D normal = new Vector3D(tmpvec.dX, tmpvec.dY, tmpvec.dZ);

                    facesMesh.Normals.Add(normal);
                    facesMesh.Normals.Add(normal);
                    facesMesh.Normals.Add(normal);

                    facesMesh.Positions.Add(p0); position++;
                    facesMesh.Positions.Add(p1); position++;
                    facesMesh.Positions.Add(p2); position++;

                    facesMesh.TriangleIndices.Add(position - 1);
                    facesMesh.TriangleIndices.Add(position - 2);
                    facesMesh.TriangleIndices.Add(position - 3);

                    facesMesh.Normals.Add(normal);
                    facesMesh.Normals.Add(normal);
                    facesMesh.Normals.Add(normal);

                    e2 = e2.next;
                }
            }
            SolidColorBrush mybrush;
            mybrush = new SolidColorBrush();
            mybrush.Opacity = 1.0;
            Material material = new DiffuseMaterial(mybrush);
            GeometryModel3D meshModel = new GeometryModel3D(facesMesh, material);
            group.Children.Add(meshModel);


            Transform3D transformOnModelVisual3D = group.Transform;
            GeneralTransform3D t2 = (transformOnModelVisual3D as GeneralTransform3D).Inverse;
            Point3D startpoint = t2.Transform(new Point3D(0, 0, 2));
            Point3D raydirection = t2.Transform(new Point3D(0, 0, -1));
            this.mesh.computeSilhouette(new myPoint3D(startpoint.X, startpoint.Y, startpoint.Z), new myVector3D(raydirection.X, raydirection.Y, raydirection.Z));

            MeshGeometry3D silMesh = new MeshGeometry3D();
            for (i = 0; i < this.mesh.halfedges.Count; i++)
            {
                if (this.mesh.halfedges[i].flag == 0) continue;
                myPoint3D p1 = this.mesh.halfedges[i].source.point;
                myPoint3D p2 = this.mesh.halfedges[i].target.point;
                setLinesegment(silMesh, p1, p2);
            }

            material = new DiffuseMaterial(new SolidColorBrush(Colors.Red));
            GeometryModel3D silGeometry = new GeometryModel3D(silMesh, material);
            group.Children.Add(silGeometry);
        }

        private void setGUINormal()
        {
            setGUIMesh();
            MeshGeometry3D wireframeMesh = new MeshGeometry3D();

            for (int i = 0; i < mesh.faces.Count; i++)
            {
                myPoint3D p1 = mesh.faces[i].GetCentroid().point;
                myPoint3D p2 = p1 + ( mesh.faces[i].GetNormal() / 10 );
                setLinesegment(wireframeMesh, p1, p2);
            }
            GeometryModel3D wireframeGeometry = new GeometryModel3D(wireframeMesh, new DiffuseMaterial(new SolidColorBrush(Colors.Blue)));
            group.Children.Add(wireframeGeometry);
        }
    }
}
